﻿namespace NTLib.Core.Reflection
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using NTLib.Core.Exceptions;
    using System.Linq.Expressions;
    using Attributes;

    /// <summary>
    /// Extensions methods use to simplify the type manipulation.
    /// </summary>
    public static class TypeExtensions
    {
        #region Fields

        /// <summary>
        /// Define the empty parameters that could be used in the invocation of a parameter less function.
        /// </summary>
        public static readonly object[] EmptyParameters = new object[0];

        #endregion

        #region IsOrInheritOf

        /// <summary>
        /// Determines whether the current type is or inherit of the specified type to check.
        /// </summary>
        /// <param name="t">The curren type.</param>
        /// <param name="toCheck">the type that could be it or a parent.</param>
        /// <returns><c>True</c> if the curren type is or inherit from the type in parameter; otherwise false;</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool IsOrInheritOf(this object t, Type toCheck)
        {
            return toCheck.GetTypeInfo().IsAssignableFrom(t.GetType().GetTypeInfo());
        }

        /// <summary>
        /// Determines whether the current type is or inherit of the specified type to check.
        /// </summary>
        /// <param name="t">The curren type.</param>
        /// <param name="toCheck">the type that could be it or a parent.</param>
        /// <returns><c>True</c> if the curren type is or inherit from the type in parameter; otherwise false;</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool IsOrInheritOf(this object t, TypeInfo toCheck)
        {
            return toCheck.IsAssignableFrom(t.GetType().GetTypeInfo());
        }

        /// <summary>
        /// Determines whether the current type is or inherit of the specified type to check.
        /// </summary>
        /// <param name="t">The curren type.</param>
        /// <param name="toCheck">the type that could be it or a parent.</param>
        /// <returns><c>True</c> if the curren type is or inherit from the type in parameter; otherwise false;</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool IsOrInheritOf(this Type t, Type toCheck)
        {
            return toCheck.GetTypeInfo().IsAssignableFrom(t.GetTypeInfo());
        }

        /// <summary>
        /// Determines whether the current type is or inherit of the specified type to check.
        /// </summary>
        /// <param name="t">The curren type.</param>
        /// <param name="toCheck">the type that could be it or a parent.</param>
        /// <returns><c>True</c> if the curren type is or inherit from the type in parameter; otherwise false;</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool IsOrInheritOf(this Type t, TypeInfo toCheck)
        {
            return toCheck.IsAssignableFrom(t.GetTypeInfo());
        }

        /// <summary>
        /// Determines whether the current type is or inherit of the specified type to check.
        /// </summary>
        /// <param name="t">The curren type.</param>
        /// <param name="toCheck">the type that could be it or a parent.</param>
        /// <returns><c>True</c> if the curren type is or inherit from the type in parameter; otherwise false;</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool IsOrInheritOf(this TypeInfo t, TypeInfo toCheck)
        {
            return toCheck.IsAssignableFrom(t);
        }

        /// <summary>
        /// Determines whether the current type is or inherit of the specified type to check.
        /// </summary>
        /// <param name="t">The curren type.</param>
        /// <param name="toCheck">the type that could be it or a parent.</param>
        /// <returns><c>True</c> if the curren type is or inherit from the type in parameter; otherwise false;</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool IsOrInheritOf(this TypeInfo t, Type toCheck)
        {
            return toCheck.GetTypeInfo().IsAssignableFrom(t);
        }

        #endregion

        #region GetMandatoryAttribute

        /// <summary>
        /// Define if the type have the specific attribute
        /// </summary>
        /// <typeparam name="T">The attribute your are looking for.</typeparam>
        /// <param name="type">The current type.</param>
        /// <param name="inherit">if set to <c>true</c> to check the inheritance. Default false.</param>
        /// <returns><c>True</c> If the type if flag by the specific attribute</returns>
        public static T GetMandatoryAttribute<T>(this TypeInfo type, bool inherit = false)
            where T : Attribute
        {
            var attr = type.GetCustomAttribute<T>(inherit);
            if (attr == null)
                throw new AttributeMandatoryException(typeof(T), type);
            return attr;
        }


        /// <summary>
        /// Define if the type have the specific attribute
        /// </summary>
        /// <typeparam name="T">The attribute your are looking for.</typeparam>
        /// <param name="type">The current type.</param>
        /// <param name="inherit">if set to <c>true</c> to check the inheritance. Default false.</param>
        /// <returns><c>True</c> If the type if flag by the specific attribute</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static T GetMandatoryAttribute<T>(this Type type, bool inherit = false)
            where T : Attribute
        {
            return type.GetTypeInfo().GetMandatoryAttribute<T>(inherit);
        }

        /// <summary>
        /// Define if the type have the specific attribute
        /// </summary>
        /// <typeparam name="T">The attribute your are looking for.</typeparam>
        /// <param name="obj">The object.</param>
        /// <param name="inherit">if set to <c>true</c> to check the inheritance. Default false.</param>
        /// <returns>
        ///   <c>True</c> If the type if flag by the specific attribute
        /// </returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static T GetMandatoryAttribute<T>(this object obj, bool inherit = false)
            where T : Attribute
        {
            return obj.GetType().GetMandatoryAttribute<T>(inherit);
        }

        /// <summary>
        /// Define if the type have the specific attribute
        /// </summary>
        /// <param name="type">The current type.</param>
        /// <param name="T">The t.</param>
        /// <param name="inherit">if set to <c>true</c> to check the inheritance. Default false.</param>
        /// <returns>
        ///   <c>True</c> If the type if flag by the specific attribute
        /// </returns>
        /// <exception cref="NTLib.Core.Exceptions.AttributeMandatoryException"></exception>
        public static object GetMandatoryAttribute(this TypeInfo type, Type T, bool inherit = false)
        {
            var attr = type.GetCustomAttribute(T, inherit);
            if (attr == null)
                throw new AttributeMandatoryException(T, type);
            return attr;
        }


        /// <summary>
        /// Define if the type have the specific attribute
        /// </summary>
        /// <param name="type">The current type.</param>
        /// <param name="T">The t.</param>
        /// <param name="inherit">if set to <c>true</c> to check the inheritance. Default false.</param>
        /// <returns>
        ///   <c>True</c> If the type if flag by the specific attribute
        /// </returns>
        /// <exception cref="InvalidCastException"></exception>
        public static object GetMandatoryAttribute(this Type type, Type T, bool inherit = false)
        {
            if (!T.IsOrInheritOf(typeof(Attribute)))
                throw new InvalidCastException();

            return type.GetTypeInfo().GetMandatoryAttribute(T, inherit);
        }

        /// <summary>
        /// Define if the type have the specific attribute
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="T">The t.</param>
        /// <param name="inherit">if set to <c>true</c> to check the inheritance. Default false.</param>
        /// <returns>
        ///   <c>True</c> If the type if flag by the specific attribute
        /// </returns>
        /// <exception cref="InvalidCastException"></exception>
        public static object GetMandatoryAttribute(this object obj, Type T, bool inherit = false)
        {
            if (!T.IsOrInheritOf(typeof(Attribute)))
                throw new InvalidCastException();

            return obj.GetType().GetMandatoryAttribute(T, inherit);
        }


        #endregion

        #region AsAttr

        /// <summary>
        /// Define if the type have the specific attribute
        /// </summary>
        /// <typeparam name="T">The attribute your are looking for.</typeparam>
        /// <param name="type">The current type.</param>
        /// <param name="inherit">if set to <c>true</c> to check the inheritance. Default false.</param>
        /// <returns><c>True</c> If the type if flag by the specific attribute</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool HasAttr<T>(this TypeInfo type, bool inherit = false)
            where T : Attribute
        {
            return type.GetCustomAttribute<T>(inherit) != null;
        }


        /// <summary>
        /// Define if the type have the specific attribute
        /// </summary>
        /// <typeparam name="T">The attribute your are looking for.</typeparam>
        /// <param name="type">The current type.</param>
        /// <param name="inherit">if set to <c>true</c> to check the inheritance. Default false.</param>
        /// <returns><c>True</c> If the type if flag by the specific attribute</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool HasAttr<T>(this Type type, bool inherit = false)
            where T : Attribute
        {
            return type.GetTypeInfo().HasAttr<T>(inherit);
        }

        /// <summary>
        /// Define if the type have the specific attribute
        /// </summary>
        /// <typeparam name="T">The attribute your are looking for.</typeparam>
        /// <param name="obj">The object.</param>
        /// <param name="inherit">if set to <c>true</c> to check the inheritance. Default false.</param>
        /// <returns>
        ///   <c>True</c> If the type if flag by the specific attribute
        /// </returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool HasAttr<T>(this object obj, bool inherit = false)
            where T : Attribute
        {
            return obj.GetType().HasAttr<T>(inherit);
        }

        /// <summary>
        /// Determines whether the specified inherit has attribute.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="member">The member.</param>
        /// <param name="inherit">if set to <c>true</c> [inherit].</param>
        /// <returns>
        ///   <c>true</c> if the specified inherit has attribute; otherwise, <c>false</c>.
        /// </returns>
        public static bool HasAttr<T>(this MemberInfo member, bool inherit = false)
            where T : Attribute
        {
            return member.GetCustomAttribute<T>(inherit) != null;
        }

        /// <summary>
        /// Define if the type have the specific attribute
        /// </summary>
        /// <param name="type">The current type.</param>
        /// <param name="T">The t.</param>
        /// <param name="inherit">if set to <c>true</c> to check the inheritance. Default false.</param>
        /// <returns>
        ///   <c>True</c> If the type if flag by the specific attribute
        /// </returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool HasAttr(this TypeInfo type, Type T, bool inherit = false)
        {
            return type.GetCustomAttribute(T, inherit) != null;
        }


        /// <summary>
        /// Define if the type have the specific attribute
        /// </summary>
        /// <param name="type">The current type.</param>
        /// <param name="T">The t.</param>
        /// <param name="inherit">if set to <c>true</c> to check the inheritance. Default false.</param>
        /// <returns>
        ///   <c>True</c> If the type if flag by the specific attribute
        /// </returns>
        /// <exception cref="InvalidCastException"></exception>
        public static bool HasAttr(this Type type, Type T, bool inherit = false)
        {
            if (!T.IsOrInheritOf(typeof(Attribute)))
                throw new InvalidCastException();

            return type.GetTypeInfo().HasAttr(T, inherit);
        }

        /// <summary>
        /// Define if the type have the specific attribute
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="T">The t.</param>
        /// <param name="inherit">if set to <c>true</c> to check the inheritance. Default false.</param>
        /// <returns>
        ///   <c>True</c> If the type if flag by the specific attribute
        /// </returns>
        /// <exception cref="InvalidCastException"></exception>
        public static bool HasAttr(this object obj, Type T, bool inherit = false)
        {
            if (!T.IsOrInheritOf(typeof(Attribute)))
                throw new InvalidCastException();

            return obj.GetType().HasAttr(T, inherit);
        }

        #endregion

        #region Type ID

        /// <summary>
        /// Gets the type identifier GUID of the current type <typeparamref name="T"/>.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Guid GetTypeId<T>(this T obj)
        {
            return GetId(typeof(T));
        }

        /// <summary>
        /// Gets the type identifier.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns></returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Guid GetId(this Type type)
        {
            GuidAttribute attr = type.GetMandatoryAttribute<GuidAttribute>(true);
            return attr.Value;
        }

        #endregion

        #region GetRuntimeMethod

        /// <summary>
        /// Returns an object that represents the specified public method declared by the
        /// current type.
        /// </summary>
        /// <typeparam name="T">The type you want to extract the method info.</typeparam>
        /// <param name="express">The lambda use to define the method name your want.</param>
        /// <returns>
        /// An object that represents the specified method, if found; otherwise, null.
        /// </returns>
        public static MethodInfo GetDeclaredMethod<T>(Expression<Action<T>> express)
        {
            MethodCallExpression member = express.Body as MethodCallExpression;

            if (member.Method.IsGenericMethod && !member.Method.IsGenericMethodDefinition)
                return member.Method.GetGenericMethodDefinition();
            return member.Method;
        }

        #endregion
    }
}
